﻿ 
using System;
using System.Linq;
using Type=schemas.microsoft.com.linqtosql.dbml.Item2007.Type;
using XRoot=schemas.microsoft.com.linqtosql.dbml.Item2007.XRoot;
using schemas.microsoft.com.linqtosql.dbml.Item2007;
using System.Collections.Generic;

namespace LinqToDbmlRunner
{
	public static class Extensions
	{
		public static Table Table(this XRoot root, string tableName)
		{
			return root.Database.Table.First(t => t.Name == "dbo." + tableName);
		}
		public static Type Type(this XRoot root, string typeName)
		{
			foreach (var table in root.Database.Table)
			{
				var type = Type(table.Type, typeName);
				if (type != null) return type;
			}
			return null;
		}

		private static Type Type(Type typeToSearch,string typeName)
		{
			if (typeToSearch.Name == typeName) return typeToSearch;
			foreach(var childType in typeToSearch.Type1)
			{
				var type = Type(childType, typeName);
				if (type != null) return type;
			}
			return null;
		}
		public static IEnumerable<Type> AllChildTypes(this Type parent)
		{
			foreach (var childType in parent.Type1)
			{
				yield return childType;
				foreach (var childOfChildType in childType.AllChildTypes())
				{
					yield return childOfChildType;
				}
			}
		}

		public static Association Association(this Type type, string assocationName)
		{
			var assoc = type.Association.FirstOrDefault(t => t.Name == assocationName);
			if (assoc == null)
			{
				foreach (var child in type.Type1)
				{
					assoc = child.Association(assocationName);
					if (assoc != null) break;
				}
			}
			return assoc;
		}
	 
		public static Column Column(this Type type, string columnName)
		{
			return type.Column.First(t => t.Name == columnName);
		}
		public static Column Move(this Column column, Type source, Type dest)
		{
			source.Column.Remove(column);
			dest.Column.Add(column);
			return column;
		}
		public static Association Move(this Association association, Type source, Type dest)
		{
			source.Association.Remove(association);
			dest.Association.Add(association);
			return association;
		}
		public static string Singularize(this string s)
		{
			if (s.EndsWith("ies")) return s.Substring(0, s.Length - 3) + "y";
			if (s.EndsWith("s")) return s.Substring(0, s.Length - 1);
			return s;
		}
		public static T Apply<T>(this T t, Action<T> action)
		{
			action(t);
			return t;
		}
		public static XRoot ExtrapolateSubclasses(this XRoot root)
		{

			foreach (var table in root.Database.Table)
			{


				var types = new List<Type>() {table.Type};
				var inheritanceCodeQuery =
					(from t in table.Type.Type1 where t.InheritanceCode != null select int.Parse(t.InheritanceCode));
				var maxInheritanceCode = inheritanceCodeQuery.Any() ? inheritanceCodeQuery.Max() : 0;
				var columnsInInheritors = (from c in table.Type.Column
				                           where c.Name.Contains("_")
				                           select c).ToArray();

				if (columnsInInheritors.Any())
				{
					table.Type.Column.First(c => c.Name == table.Type.Name + "Type").IsDiscriminator = true;
					table.Type.InheritanceCode = "0";
					table.Type.IsInheritanceDefault = true;
					foreach (var c in columnsInInheritors)
					{
						c.CanBeNull = false;
						var parts = c.Name.Split('_');
						for (int i = 0; i < parts.Length - 1; i++)
						{
							string typeName = parts[i];
							if (!types.Any(t => t.Name == typeName))
							{
								var newType = new Type()
								{
									Name = typeName,
									InheritanceCode = (maxInheritanceCode + types.Count).ToString()
								};
								types.Add(newType);
								string parentTypeName = i == 0 ? table.Type.Name : parts[i - 1];
								types.First(t => t.Name == parentTypeName).Type1.Add(newType);
							}
						}
						table.Type.Column.Remove(c);
						c.Member = parts[parts.Length - 1];
						types.First(t => t.Name == parts[parts.Length - 2]).Column.Add(c);
					}


				}
			}
			foreach (var table in root.Database.Table)
			{

				foreach (var association in table.Type.Association.Where(a => a.OtherKey != null).ToArray())
				{
					var parts = (association.Type + "_" + association.OtherKey).Split('_');
					association.OtherKey = parts[parts.Length - 1];
					association.Type = parts[parts.Length - 2];
					association.Member = parts[parts.Length - 2] + "s";
					table.Type.Association.Remove(association);
					root.Type(association.Name.Split('_')[1]).Association.Add(association);
					if (association.Cardinality == "One")
					{
						association.Member = association.Member.Singularize();
						association.Name = association.Name.Singularize();
					}
				}
			

				foreach (var association in table.Type.Association.Where(a => a.ThisKey != null).ToArray())
				{
					var parts = (association.Type + "_" + association.ThisKey).Split('_');
					var thisKey = parts[parts.Length - 1];
					association.ThisKey = thisKey;
					association.Member = thisKey.Substring(0, thisKey.Length - 2);
					association.Type = association.Member.Singularize();
					table.Type.Association.Remove(association);
					root.Type(association.Name.Substring(association.Name.LastIndexOf('_') + 1).Singularize()).Association.Add(association);
				}
			}

			return root;
		}

	}
}
